'''
Created on 30/03/2009

@author: eh01
'''
from M2Crypto import SSL, httpslib
from im.ds import dsconstants
from im.ds.dsstub import DSStub
from im.ds.dst import DST
from im.event.event import Event
from im.event.eventhandler import BaseEventHandler
from im.event.eventmanager import EventManager
from urlparse import urlparse
from im.event import eventconstants
import datetime
import httplib
import os

class SPEARA2AHandler(BaseEventHandler):
    '''
    classdocs
    '''

    __ssl_context = None
    __spear_a2a_base_url = None
    
    def __init__(self):
        '''
        Constructor
        '''
        ds_stub = DSStub()
        self.__ssl_context = self.initialize_ssl_context()
        self.__spear_a2a_base_url = ds_stub.get_spear_a2a_base_url()

    def ssl_passphrase(self, v):
        """
        This method is called when the phasephrase is needed.
        See C:\ActivePython26\Lib\site-packages\M2Crypto\util.py :: passphrase_callback
        The password variable is assumed to be in scope when this method is called. Not very tidy.
        """
        ds_stub = DSStub()
        return ds_stub.get_client_cert_password()
    
    def get_ssl_context(self):
        if self.__ssl_context:
            return self.__ssl_context
        else:
            self.__ssl_context = self.initialize_ssl_context()
    
    def initialize_ssl_context(self):
        ds_stub = DSStub()        
        # A bundle of root certificates that includes the roots for SPEAR CA.
        ca_cert_path = ds_stub.get_ca_cert()
        # A client certificate and password defined for Banyule in the SPEAR A2A (S&V) environment
        # *** None of the following files or passwords may be included in publically accessable code or files. ***
        client_cert_path = ds_stub.get_client_cert()
        ctx = self.set_ssl_context(ca_cert_path, client_cert_path, self.ssl_passphrase)
        return ctx
        
    def set_ssl_context(self, ca_cert, client_cert, passphrase):
        ctx = SSL.Context('sslv23')
        ctx.load_client_CA(ca_cert)
        ctx.load_cert(client_cert, callback=passphrase)
        if ctx.load_verify_locations(ca_cert) != 1:
            raise Exception('No CA certs')
        ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
        #ctx.set_info_callback()
        return ctx

    def https_req(self, ctx, method, uri, followredirect=False, body=None):
        """
        HTTP request for a client certificate protected SSL encrypted resource.
        If followredirect=True, then follow redirection (302) forever.
        Returns the response object.
        """
        parsed_uri = urlparse(uri)
        parse_the_rest = ''
        if parsed_uri.query is '':
            parse_the_rest = parsed_uri.path
        else:
            parse_the_rest = '{0}?{1}'.format(parsed_uri.path, parsed_uri.query)
        h = httpslib.HTTPSConnection(parsed_uri.hostname, parsed_uri.port, ssl_context=ctx)
        h.set_debuglevel(0)
        h.putrequest(method, parse_the_rest)
        #h.putheader('Accept', 'application/xml')
        #h.putheader('Accept', 'text/xml')
        h.putheader('SOAPAction', '')
        h.putheader('Connection', 'close')
        if body is not None:
            h.putheader('Content-Type', 'application/xml')
            h.putheader('Content-Length', len(body))
            h.endheaders()
            h.send(body)
        else:
            h.endheaders()
        resp = h.getresponse()
        #print('HTTP Status: {0}'.format(resp.status))
        if followredirect is not None:
            if resp.status == httplib.FOUND:
                redirect = resp.getheader('Location')
                print('Redirect to ({0}): {1}'.format(httplib.FOUND, redirect))
                return self.https_req(ctx, method, redirect, followredirect)
        if resp.status == httplib.OK:
            return resp
        else:
            raise RequestError(resp.status, 'HTTP Error - ' + resp.reason)            
        
    def https_post(self, ctx, uri, body, followredirect=False):
        """
        HTTP GET for a client certificate protected SSL encrypted resource.
        Returns None if there is an error.
        """
        resp = self.https_req(ctx, 'POST', uri, followredirect, body)
        return resp.read()
            
    def do(self, data):
        dso = data
        spear_a2a_web_service_name_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                    predicate=dsconstants.PREDICATE_SPEAR_A2A_WEB_SERVICE_NAME)
        spear_a2a_web_service_name = spear_a2a_web_service_name_dst.get_literal()
        soap_message_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                    predicate=dsconstants.PREDICATE_SOAP_MESSAGE)
        soap_message = soap_message_dst.get_literal()        

        response = None
        event_type = None
        url = self.__spear_a2a_base_url + spear_a2a_web_service_name
        try:
            start = datetime.datetime.now()
            print 'sending request to SPEAR' + str(start)
            response = self.https_post(self.get_ssl_context(), url, soap_message, followredirect=True)
            end = datetime.datetime.now()
            print 'response from SPEAR' + str(end)
            print 'time taken' + str(end - start)

            web_service_response_event_type_dst = dso.get_dst(subject=dsconstants.SUBJECT_ROOT,
                           predicate=dsconstants.PREDICATE_WEB_SERVICE_RESPONSE_EVENT_TYPE)
            web_service_response_event_type = web_service_response_event_type_dst.get_literal()
            
            web_service_response_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                predicate=dsconstants.PREDICATE_WEB_SERVICE_RESPONSE,
                literal=response)            
            # update dso
            dso.add_dst(web_service_response_dst)
            event_type = web_service_response_event_type            
        except RequestError as err:
            (status, reason) = err
            error_code_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                predicate=dsconstants.PREDICATE_ERROR_CODE, 
                literal=status)
            error_message_dst = DST(subject=dsconstants.SUBJECT_ROOT, 
                predicate=dsconstants.PREDICATE_ERROR_MESSAGE, 
                literal=reason)
            dso.add_dst(error_code_dst)
            dso.add_dst(error_message_dst)
            event_type = eventconstants.SPEAR_A2A_FAULT_WEB_SERVICE_EVENT_TYPE
            print('Request Error: {1} ({0})'.format(status, reason))
        
        # remove dsts
        dso.remove_dst(subject=dsconstants.SUBJECT_ROOT,
                       predicate=dsconstants.PREDICATE_SPEAR_A2A_WEB_SERVICE_NAME)
        dso.remove_dst(subject=dsconstants.SUBJECT_ROOT,
                       predicate=dsconstants.PREDICATE_SOAP_MESSAGE)
        dso.remove_dst(subject=dsconstants.SUBJECT_ROOT,
                           predicate=dsconstants.PREDICATE_WEB_SERVICE_RESPONSE_EVENT_TYPE)
        event = Event(event_type, dso)
        EventManager().add_event(event)
            

class RequestError(Exception):
    """Base class for request related errors."""            